package com.tarena.lbs.coupon.service.impl;

import com.tarena.lbs.activity.api.ActivityApi;
import com.tarena.lbs.activity.api.pojo.ActivityDto;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.common.principle.UserPrinciple;
import com.tarena.lbs.common.utils.AuthenticationContextUtils;
import com.tarena.lbs.coupon.api.pojo.param.UserCouponsParam;
import com.tarena.lbs.coupon.base.constant.CouponRedisKey;
import com.tarena.lbs.coupon.base.enums.CouponStatusEnum;
import com.tarena.lbs.coupon.base.exception.ServiceException;
import com.tarena.lbs.coupon.base.response.StatusCode;
import com.tarena.lbs.coupon.dao.repository.CouponCodeRepository;
import com.tarena.lbs.coupon.dao.repository.CouponRepository;
import com.tarena.lbs.coupon.dao.repository.StockRepository;
import com.tarena.lbs.coupon.dao.repository.UserCouponsRepository;
import com.tarena.lbs.coupon.pojo.param.CouponParam;
import com.tarena.lbs.coupon.pojo.vo.CouponVO;
import com.tarena.lbs.coupon.pojo.vo.UserCouponsVO;
import com.tarena.lbs.coupon.service.CouponService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author ZHC
 */
@Service
@Slf4j
public class CouponServiceImpl implements CouponService {
    @Autowired
    private CouponRepository couponRepository;
    @Autowired
    private CouponCodeRepository couponCodeRepository;
    @Autowired
    private StockRepository stockRepository;
    @Autowired
    private UserCouponsRepository userCouponsRepository;
    @Autowired
    private RedisTemplate redisTemplate;

    @DubboReference
    private ActivityApi activityApi;

    @Override
    public void addCoupon(CouponParam couponParam) {
        // 根据验权信息获取商家ID
        Integer businessId = checkPermission();
        couponParam.setBusinessId(businessId);

        if (couponParam.getId() != null){
            // 修改优惠券基础数据
            couponRepository.updateCoupon(couponParam);
        }else {
            // 1.新增优惠券基础数据
            Integer couponId = couponRepository.instertCoupon(couponParam);
            // 2.新增优惠券code
            couponCodeRepository.insertCouponCode(couponId, couponParam);
            // 3.新增优惠券库存
            stockRepository.insertCouponStock(couponId, couponParam);
            log.info("Coupon业务层:新增优惠券couponId:{}",couponId);
            log.info("Coupon业务层:新增优惠券couponParam:{}",couponParam);
            // 4.把优惠券库存写入redis
            stockRepository.insertCouponStockInRedis(couponId,couponParam);
        }
    }

    @Override
    public void receiveCoupon(UserCouponsParam param) {
        // 1.验证用户，根据验权信息获取userId
        Integer userId = checkPermission();
        param.setUserId(userId);
        // 2.验证活动
        checkActivity(userId,param.getActivityId());
        // 3.验证优惠券
        checkCoupon(userId,param.getCouponId());
        // 4.抢锁: 最多抢3次;睡1s;锁有自动释放的时间  set key value NX EX 5
        // 设计一个key值,多个线程 操作同一个优惠券 key相同
        String lockKey = CouponRedisKey.COUPON_RECEIVE_KEY+param.getCouponId();
        ValueOperations valueOps = redisTemplate.opsForValue();
        // 判断如果抢到了 就执行业务,没抢到就等待200毫秒,计数最多重抢3次
        Boolean haveLock=false;
        int count=0;//计数器
        String lockValue = UUID.randomUUID().toString();
        try{
            do {
                //第一次直接抢 count>0&&count<4 第234次 等待200ms
                if (count >= 1 && count < 4) {
                    Thread.sleep(200);
                } else if (count >= 4) {
                    log.error("你抢了3次都没抢到,放弃");
                    return;
                }
//                haveLock = valueOps.setIfAbsent(lockKey, "", 5, TimeUnit.SECONDS);
                haveLock = valueOps.setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS);
                count++;
            } while (!haveLock);
            log.debug("抢锁成功");
            // 5.验证优惠券redis库存是否足够
            checkCouponRedisStock(param.getCouponId());
            // 6.领取优惠券
            userCouponsRepository.insertCouponCode(param);
            // 7.修改优惠券库存
            stockRepository.updateCouponStock(param.getCouponId());
            // 8.修改优惠券redis库存
            stockRepository.updateCouponStockInRedis(param.getCouponId());
        }catch (ServiceException e){
            log.error("优惠券领取失败",e);
            throw e;
        }catch (Exception e) {
            log.error("分布式锁落地过程出现问题",e);
            throw new ServiceException(StatusCode.COUPON_COLLECTION_FAILED);
        }finally{
            //释放锁
//            redisTemplate.delete(lockKey);
            // 使用 Lua 脚本安全删除锁
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(lockKey), lockValue);
        }
    }

    private void checkCouponRedisStock(Integer couponId) {
        log.debug("Coupon业务层:验证优惠券:{}库存",couponId);
        String key = CouponRedisKey.COUPON_STOCK_KEY + couponId;
        // 如果优惠券redis库存key不存在
        if (!redisTemplate.hasKey(key)) {
            return;
        }
        HashOperations hashOperations = redisTemplate.opsForHash();
        // 如果优惠券库存不够
        if ((Long) hashOperations.get(key, "num") <= 0) {
            log.error("Coupon业务层:优惠券:{}库存不足",couponId);
            throw new ServiceException(StatusCode.COUPON_IS_GONE);
        }
    }

    private void checkCoupon(Integer userId, Integer couponId) {
        log.debug("Coupon业务层:验证优惠券:{}",couponId);
        // 3.1 验证优惠券是否存在
        CouponVO couponVO = couponDetail(couponId);
        if (couponVO == null) {
            log.error("Coupon业务层:验证优惠券:{}不存在",couponId);
            throw new ServiceException(StatusCode.COUPON_NOT_EXIST);
        }
        // 3.2 验证优惠券的状态是否是激活
        if (!couponVO.getStatus().equals(CouponStatusEnum.STATUS_ACTIVATE.getCode())) {
            log.error("Coupon业务层:验证优惠券:{}状态异常",couponId);
            throw new ServiceException(StatusCode.COUPON_STATUS_ERROR);
        }
        // 3.3 验证用户领取优惠券的检查条件 领取上限
        // 比如当前优惠券 一个用户只能领取一张，意味这用户领取的时候要判断 判断用户是否已经领取过，如果领取过了，就不允许用户领取
        List<UserCouponsVO> userCouponsVOS = userCouponsRepository.selectByUserIdAndCouponId(userId,couponId);
        // 判断用户领取次数是否超过最大领取次数
        if (userCouponsVOS.size() >= couponVO.getUsageLimit()){
            log.error("Coupon业务层:验证优惠券:{}用户已经领取过",couponId);
            throw new ServiceException(StatusCode.COUPON_USER_LIMIT);
        }

    }

    private void checkActivity(Integer userId, Integer activityId) {
        log.debug("Coupon业务层:验证活动:{}",activityId);
        // 2.1 验证活动是否存在
        ActivityDto activityDto = activityApi.getActivityById(activityId);
        if (activityDto == null) {
            log.error("Coupon业务层:验证活动:{}不存在",activityId);
            throw new ServiceException(StatusCode.ACTIVITY_NOT_EXIST);
        }
        // 2.2. 验证活动有效期 领取优惠券的时间 是否 在 活动的结束时间 之前
        if (activityDto.getEndDate().getTime() < System.currentTimeMillis()){
            log.error("Coupon业务层:验证活动:{}已结束",activityId);
            throw new ServiceException(StatusCode.ACTIVITY_END);
        }
        // 2.3 验证活动状态  活动是否启用
        if (activityDto.getStatus() != 1){
            log.error("Coupon业务层:验证活动:{}未开始或已结束",activityId);
            throw new ServiceException(StatusCode.ACTIVITY_END);
        }
        // 2.4 验证用户是否是活动的目标用户,标签是否匹配
        if (!activityApi.activityVisible(userId,activityId)){
            log.error("Coupon业务层:验证活动:{}用户不符合活动目标人群",activityId);
            throw new ServiceException(StatusCode.ACTIVITY_USERS_NOT_MATCH);
        }
    }

    @Override
    public PageResult<CouponVO> pageList(Integer pageNum, Integer pageSize) {
        log.info("Coupon业务层:查询优惠券分页列表:{},{}",pageNum,pageSize);
        // 根据验权信息获取商家ID
        Integer businessId = checkPermission();
        return couponRepository.selectPageList(pageNum, pageSize,businessId);
    }

    @Override
    public CouponVO couponDetail(Integer id) {
        log.info("Coupon业务层:查询优惠券详情:{}",id);
        return couponRepository.selectById(id);
    }


    private Integer checkPermission() {
        UserPrinciple userPrinciple = AuthenticationContextUtils.get();
        return userPrinciple.getId();
    }
}
